Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / core / data / src / commonTest / kotlin / org / meshtastic / core / data / ai / AiFunctionProviderImplTest.kt

Displaying Raw • Download

core/data/src/commonTest/kotlin/org/meshtastic/core/data/ai/AiFunctionProviderImplTest.kt 01cd54907d62cd90913d3d071b6bc240cae365ef (01cd5490) Text, 11.59 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.ai

Tff7b72import T7ee787dev.mokkery.MockMode
Tff7b72import T7ee787dev.mokkery.answering.returns
Tff7b72import T7ee787dev.mokkery.every
Tff7b72import T7ee787dev.mokkery.mock
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.flowOf
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787org.meshtastic.core.model.ConnectionState
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.PacketRepository
Tff7b72import T7ee787org.meshtastic.core.repository.RadioConfigRepository
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceRepository
Tff7b72import T7ee787org.meshtastic.core.repository.usecase.SendMessageUseCase
Tff7b72import T7ee787org.meshtastic.proto.User
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertIs
Tff7b72import T7ee787kotlin.test.assertNull
Tff7b72import T7ee787kotlin.time.Clock
Tff7b72import T7ee787kotlin.time.Instant

Tff7b72class T56d364AiFunctionProviderImplTest Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3connectionState Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3ConnectionStateTff7b72>Tb4b4b4(Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4)
Tff7b72private Tff7b72val Te6edf3serviceRepositoryTb4b4b4: Te6edf3ServiceRepository Tff7b72=
Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4) Tb4b4b4{ Te6edf3every Tb4b4b4{ Te6edf3connectionState Tb4b4b4} Te6edf3returns Tff7b72thisTf0883e@AiFunctionProviderImplTest.connectionState Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepository Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3radioConfigRepositoryTb4b4b4: Te6edf3RadioConfigRepository Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3sendMessageUseCaseTb4b4b4: Te6edf3SendMessageUseCase Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3fuzzyNameResolver Tff7b72= Te6edf3FuzzyNameResolverTb4b4b4(Te6edf3nodeRepositoryTb4b4b4, Te6edf3radioConfigRepositoryTb4b4b4)
Tff7b72private Tff7b72val Te6edf3packetRepositoryTb4b4b4: Te6edf3PacketRepository Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3clock Tff7b72= Te6edf3TestClockTb4b4b4(Te6edf3InstantTb4b4b4.Te6edf3fromEpochSecondsTb4b4b4(T79c0ff1Te6edf3_700_000_000Tb4b4b4)Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3rateLimiter Tff7b72= Te6edf3RateLimiterTb4b4b4(Te6edf3clockTb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffcreateProviderTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3AiFunctionProviderImplTb4b4b4(
Te6edf3serviceRepository Tff7b72= Te6edf3serviceRepositoryTb4b4b4,
Te6edf3nodeRepository Tff7b72= Te6edf3nodeRepositoryTb4b4b4,
Te6edf3radioConfigRepository Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4,
Te6edf3sendMessageUseCase Tff7b72= Te6edf3sendMessageUseCaseTb4b4b4,
Te6edf3fuzzyNameResolver Tff7b72= Te6edf3fuzzyNameResolverTb4b4b4,
Te6edf3packetRepository Tff7b72= Te6edf3packetRepositoryTb4b4b4,
Te6edf3rateLimiter Tff7b72= Te6edf3rateLimiterTb4b4b4,
Te6edf3clock Tff7b72= Te6edf3clockTb4b4b4,
Tb4b4b4)

T8b949e// --- getNodeDetails tests ---

Tf0883e@Test
Tff7b72fun Td2a8ffgetNodeDetails_returns_not_connected_when_disconnectedTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3Disconnected
Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getNodeDetailsTb4b4b4(Ta5d6ff"Ta5d6ff!abc123Ta5d6ff"Tb4b4b4)
Te6edf3assertIsTff7b72<Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3NotConnectedTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetNodeDetails_returns_not_found_for_unknown_nodeTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3emptyMapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getNodeDetailsTb4b4b4(Ta5d6ff"Ta5d6ff!ffffffTa5d6ff"Tb4b4b4)

Te6edf3assertIsTff7b72<Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3NotFoundTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetNodeDetails_returns_node_data_for_valid_hex_idTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3testNode Tff7b72=
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= T79c0ff0Te6edf3xabcTb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!00000abcTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffAliceTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffALTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3lastHeard Tff7b72= T79c0ff1Te6edf3_700_000_000Tb4b4b4,
Te6edf3snr Tff7b72= T79c0ff5.5fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff7T79c0ff0Tb4b4b4,
Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3hopsAway Tff7b72= T79c0ff1Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3mapOfTb4b4b4(T79c0ff0Te6edf3xabc Te6edf3to Te6edf3testNodeTb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getNodeDetailsTb4b4b4(Ta5d6ff"Ta5d6ff!abcTa5d6ff"Tb4b4b4)

Te6edf3assertIsTff7b72<Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3SuccessTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffAliceTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff5.5fTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3snrTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Tff7b72-T79c0ff7T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3rssiTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3hopsAwayTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetNodeDetails_returns_null_position_when_no_fixTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
T8b949e// Node with (0.0, 0.0) position and time=0 → no valid position
Tff7b72val Te6edf3testNode Tff7b72= Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!00000001Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffNoGPSTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffNGTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3mapOfTb4b4b4(T79c0ff1 Te6edf3to Te6edf3testNodeTb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getNodeDetailsTb4b4b4(Ta5d6ff"Ta5d6ff!1Ta5d6ff"Tb4b4b4)

Te6edf3assertIsTff7b72<Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3SuccessTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3latitudeTb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3longitudeTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetNodeDetails_returns_error_for_invalid_hex_formatTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3emptyMapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getNodeDetailsTb4b4b4(Ta5d6ff"Ta5d6ff!not_hexTa5d6ff"Tb4b4b4)

T8b949e// Invalid hex should result in NotFound or Error
Tff7b72val Te6edf3isHandled Tff7b72= Te6edf3result Tff7b72is Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3NotFound Tff7b72|Tff7b72| Te6edf3result Tff7b72is Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3Error
Te6edf3assertEqualsTb4b4b4(Tff7b72trueTb4b4b4, Te6edf3isHandledTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetNodeDetails_round_trips_high_bit_node_numTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
T8b949e// A node num with the high bit set (-1 == 0xFFFFFFFF) must format and parse as the canonical
T8b949e// "!ffffffff", not the signed "!-1" — regression guard for the node-ID hex fix.
Tff7b72val Te6edf3testNode Tff7b72= Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= Tff7b72-T79c0ff1Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!ffffffffTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffHighBitTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffHBTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3mapOfTb4b4b4(Tff7b72-T79c0ff1 Te6edf3to Te6edf3testNodeTb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getNodeDetailsTb4b4b4(Ta5d6ff"Ta5d6ff!ffffffffTa5d6ff"Tb4b4b4)

Te6edf3assertIsTff7b72<Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3SuccessTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff!ffffffffTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3idTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffHighBitTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3nameTb4b4b4)
Tb4b4b4}

T8b949e// --- getMeshMetrics tests ---

Tf0883e@Test
Tff7b72fun Td2a8ffgetMeshMetrics_returns_not_connected_when_disconnectedTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3Disconnected
Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getMeshMetricsTb4b4b4(Tb4b4b4)
Te6edf3assertIsTff7b72<Te6edf3GetMeshMetricsResultTb4b4b4.Te6edf3NotConnectedTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetMeshMetrics_returns_valid_metrics_with_active_nodesTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodes Tff7b72= Te6edf3mapOfTb4b4b4(T79c0ff1 Te6edf3to Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff1Te6edf3_699_999_990Tb4b4b4)Tb4b4b4, T79c0ff2 Te6edf3to Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff1Te6edf3_699_999_980Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3nodesTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3totalNodeCount Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(T79c0ff2Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3onlineNodeCount Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(T79c0ff2Tb4b4b4)

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getMeshMetricsTb4b4b4(Tb4b4b4)

Te6edf3assertIsTff7b72<Te6edf3GetMeshMetricsResultTb4b4b4.Te6edf3SuccessTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3metricsTb4b4b4.Te6edf3totalNodeCountTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3metricsTb4b4b4.Te6edf3onlineNodeCountTb4b4b4)
T8b949e// Health score: 50 + (50 * 2) / 2 = 100
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3metricsTb4b4b4.Te6edf3meshHealthScoreTb4b4b4)
T8b949e// Most recent packet: 1_699_999_990 * 1000
Te6edf3assertEqualsTb4b4b4(T79c0ff1Te6edf3_699_999_990_000LTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3metricsTb4b4b4.Te6edf3mostRecentPacketTimeTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetMeshMetrics_returns_zero_health_score_when_emptyTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3emptyMapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3totalNodeCount Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(T79c0ff0Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3onlineNodeCount Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(T79c0ff0Tb4b4b4)

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getMeshMetricsTb4b4b4(Tb4b4b4)

Te6edf3assertIsTff7b72<Te6edf3GetMeshMetricsResultTb4b4b4.Te6edf3SuccessTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3metricsTb4b4b4.Te6edf3totalNodeCountTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3metricsTb4b4b4.Te6edf3meshHealthScoreTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetMeshMetrics_falls_back_to_current_time_when_all_lastHeard_zeroTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodes Tff7b72= Te6edf3mapOfTb4b4b4(T79c0ff1 Te6edf3to Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3nodesTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3totalNodeCount Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3onlineNodeCount Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(T79c0ff0Tb4b4b4)

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getMeshMetricsTb4b4b4(Tb4b4b4)

Te6edf3assertIsTff7b72<Te6edf3GetMeshMetricsResultTb4b4b4.Te6edf3SuccessTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
T8b949e// Falls back to clock.now() since all lastHeard are 0
Te6edf3assertEqualsTb4b4b4(Te6edf3clockTb4b4b4.Te6edf3nowTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toEpochMillisecondsTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3metricsTb4b4b4.Te6edf3mostRecentPacketTimeTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffgetMeshMetrics_returns_degraded_health_when_no_nodes_onlineTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodes Tff7b72= Te6edf3mapOfTb4b4b4(T79c0ff1 Te6edf3to Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff1Te6edf3_000Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3nodesTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3totalNodeCount Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3onlineNodeCount Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(T79c0ff0Tb4b4b4)

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getMeshMetricsTb4b4b4(Tb4b4b4)

Te6edf3assertIsTff7b72<Te6edf3GetMeshMetricsResultTb4b4b4.Te6edf3SuccessTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
T8b949e// HEALTH_SCORE_DEGRADED = 10
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3metricsTb4b4b4.Te6edf3meshHealthScoreTb4b4b4)
Tb4b4b4}

T8b949e// --- sendMessage error propagation test ---

Tf0883e@Test
Tff7b72fun Td2a8ffsendMessage_returns_not_connected_when_disconnectedTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3Disconnected
Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3sendMessageTb4b4b4(Ta5d6ff"Ta5d6ffhelloTa5d6ff"Tb4b4b4, Tff7b72nullTb4b4b4, Tff7b72nullTb4b4b4)
Te6edf3assertIsTff7b72<Te6edf3SendMessageResultTb4b4b4.Te6edf3NotConnectedTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffsendMessage_returns_rate_limited_when_exhaustedTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)

T8b949e// Exhaust rate limit
Te6edf3repeatTb4b4b4(Te6edf3RateLimiterTb4b4b4.Te6edf3MAX_CALLSTb4b4b4) Tb4b4b4{ Te6edf3rateLimiterTb4b4b4.Te6edf3tryAcquireTb4b4b4(Tb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3sendMessageTb4b4b4(Ta5d6ff"Ta5d6ffhelloTa5d6ff"Tb4b4b4, Tff7b72nullTb4b4b4, Tff7b72nullTb4b4b4)
Te6edf3assertIsTff7b72<Te6edf3SendMessageResultTb4b4b4.Te6edf3RateLimitedTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}

T8b949e// --- getRecentMessages tests ---

Tf0883e@Test
Tff7b72fun Td2a8ffgetRecentMessages_contact_not_foundTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3emptyMapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3nodeMap
Te6edf3every Tb4b4b4{ Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3channelSetFlow Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3ChannelSetTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getRecentMessagesTb4b4b4(Ta5d6ff"Ta5d6ffNonExistentTa5d6ff"Tb4b4b4, T79c0ff1T79c0ff0Tb4b4b4)
Te6edf3assertIsTff7b72<Te6edf3GetRecentMessagesResultTb4b4b4.Te6edf3ContactNotFoundTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}

T8b949e// --- getUnreadSummary tests ---

Tf0883e@Test
Tff7b72fun Td2a8ffgetUnreadSummary_returns_empty_when_no_unreadTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Te6edf3every Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactsTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(Te6edf3emptyMapTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(Te6edf3emptyMapTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3channelSetFlow Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3ChannelSetTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3emptyMapTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3provider Tff7b72= Te6edf3createProviderTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3providerTb4b4b4.Te6edf3getUnreadSummaryTb4b4b4(Tb4b4b4)
Te6edf3assertIsTff7b72<Te6edf3GetUnreadSummaryResultTb4b4b4.Te6edf3SuccessTff7b72>Tb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3summaryTb4b4b4.Te6edf3totalUnreadCountTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3summaryTb4b4b4.Te6edf3contactsTb4b4b4.Te6edf3sizeTb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72class T56d364TestClockTb4b4b4(Tff7b72var Te6edf3currentTimeTb4b4b4: Te6edf3InstantTb4b4b4) Tb4b4b4: Te6edf3Clock Tb4b4b4{
Tff7b72override Tff7b72fun Td2a8ffnowTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3Instant Tff7b72= Te6edf3currentTime
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.1s